/*
 * Copyright (c) 2021
 * User:LENOVO
 * File:排列序列.java
 * Date:2021/02/18 14:18:18
 */

package org.bytedance.数组与排序;

import com.alibaba.fastjson.JSONObject;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

/**
 * 给出集合 [1,2,3,...,n]，其所有元素共有 n! 种排列。
 * 按大小顺序列出所有排列情况，并一一标记，当 n = 3 时, 所有排列如下：
 * "123"
 * "132"
 * "213"
 * "231"
 * "312"
 * "321"
 * 给定 n 和 k，返回第 k 个排列。
 */
public class 排列序列 {
    private static List<List<Integer>> res = new ArrayList<>();

    public static void main(String[] args) {
        List<Integer> permutation = getPermutation(3, 2);
        if (permutation == null) {
            System.out.println("target not found");
        } else {
            System.out.println(JSONObject.toJSONString(permutation));
        }
        String permutation1 = permutation(4, 15);
        System.out.println(permutation1);

        permutation1 = permutationUpgrade(4, 15);
        System.out.println(permutation1);
    }

    /**
     * 使用效率高的方案
     * num = Ceil(k/(n-1)!)
     * n = n -1
     * k = k - (num -1)(n-1)!
     */
    public static String permutation(int n, int k) {
        StringBuilder sb = new StringBuilder();
        Vector<Integer> v = new Vector<>();
        for (int i = 1; i <= n; i++) {
            v.add(i);
        }
        int num = 0, f = 0;
        for (int i = 1; i < n; i++) {
            k -= (num - 1) * f;
            f = factoria(n - i);
            num = (int) Math.ceil((double) k / f);
            sb.append(v.get(num - 1)).append(" ");
            v.remove(num - 1);
        }
        return sb.append(v.get(0)).toString();
    }


    /**
     直接用回溯法做的话需要在回溯到第k个排列时终止就不会超时了, 但是效率依旧感人
     可以用数学的方法来解, 因为数字都是从1开始的连续自然数, 排列出现的次序可以推
     算出来, 对于n=4, k=15 找到k=15排列的过程:

     1 + 对2,3,4的全排列 (3!个)
     2 + 对1,3,4的全排列 (3!个)         3, 1 + 对2,4的全排列(2!个)
     3 + 对1,2,4的全排列 (3!个)-------> 3, 2 + 对1,4的全排列(2!个)-------> 3, 2, 1 + 对4的全排列(1!个)-------> 3214
     4 + 对1,2,3的全排列 (3!个)         3, 4 + 对1,2的全排列(2!个)         3, 2, 4 + 对1的全排列(1!个)

     确定第一位:
     k = 14(从0开始计数)
     index = k / (n-1)! = 2, 说明第15个数的第一位是3
     更新k
     k = k - index*(n-1)! = 2
     确定第二位:
     k = 2
     index = k / (n-2)! = 1, 说明第15个数的第二位是2
     更新k
     k = k - index*(n-2)! = 0
     确定第三位:
     k = 0
     index = k / (n-3)! = 0, 说明第15个数的第三位是1
     更新k
     k = k - index*(n-3)! = 0
     确定第四位:
     k = 0
     index = k / (n-4)! = 0, 说明第15个数的第四位是4
     最终确定n=4时第15个数为3214
     **/
    public static String permutationUpgrade(int n, int k) {
        StringBuilder sb = new StringBuilder();
        Vector<Integer> vector = new Vector<>();
        for (int i = 0; i < n; i++) {
            vector.add(i + 1);
        }
        int num = 0, f = 0;
        for (int i = n - 1; i > 0; i--) {
            f = factoria(i);
            num = (int) Math.ceil((double) k / f);
            sb.append(vector.get(num - 1)).append(" ");
            k -= (num - 1) * f;
            vector.remove(num - 1);
        }
        return sb.append(vector.get(0)).toString();
    }

    /**
     * 1. 首先使用vector存储1到n数字
     * 2. 定义一个计算阶乘的函数
     * 3. k值对n-1位阶乘取整，该整数就是第一位应该从vector中所取数字的下标
     * 4. 从vecotor中取出该下标的数字，存放在string中，将改数字从vector中移除
     * 5. (k-1)值对n-1位的阶乘数取余即为下一轮循环中的开始k值，每次循环n减1，直到n为0为止。
     * @param n
     * @param k
     * @return
     */
    public static String permutationUpgradeA(int n, int k) {
        StringBuilder sb = new StringBuilder();
        Vector<Integer> vector = new Vector<>();
        for (int i = 0; i < n; i++) {
            vector.add(i + 1);
        }
        int num = 0, f = 0;
        for (int i = n - 1; i > 0; i--) {
            f = factoria(i);
            num = (int) Math.ceil((double) k / f);
            sb.append(vector.get(num - 1)).append(" ");
            k -= (num - 1) * f;
            vector.remove(num - 1);
        }
        return sb.append(vector.get(0)).toString();
    }


    private static int factoria(int n) {
        while (n > 1) {
            return n * factoria(n - 1);
        }
        return 1;
    }


    /**
     * 此方案可能导致当n和k比较大的时候，运行超时的现象
     *
     * @param n
     * @param k
     * @return
     */

    public static List<Integer> getPermutation(int n, int k) {
        LinkedList<Integer> trace = new LinkedList<>();
        core(n, k, trace);
        return res.isEmpty() || k > res.size() ? null : res.get(k - 1);
    }

    private static void core(int n, int k, LinkedList<Integer> trace) {
        if (trace.size() == n) {
            res.add(new ArrayList<>(trace));
            return;
        }
        for (int i = 1; i <= n; i++) {
            if (trace.contains(i)) continue;
            trace.add(i);
            core(n, k, trace);
            trace.removeLast();
        }
    }

}
